tags:
- OS
计算机的启动需要操作系统的支持,而操作系统通常放在非易失的辅存中。因此,我们在学习系统启动之前,先来了解一下最常见的辅存——磁盘的构成。BIOS的启动顺序如下:系统会先检查软盘驱动器,然后硬盘驱动器。之后是光盘驱动器、USB设备、最后是网络(PXE)。
在计算机中,计算机的存储结构通常是分层设计的。通过这些不同的层次使得计算机的存储同时具有高速度和大容量的特征。计算机的分层结构通常由以下几部分组成:
本系列,我们主要讨论从磁盘启动的方式。
磁盘读写数据的方式非常简单。一个磁盘由多个盘片组成,数据存放在盘片 (Platter) 表面的一圈圈同心圆上,这些同心圆称为磁道 (Track)。半径相同的磁道组成的圆柱被称为柱面 (Cylinder)。然而,单独使用磁道来存储信息并不实际,因为每个磁道的信息量太大。因此,我们需要进一步划分磁道。
我们将磁道按一定角度划分成若干扇形,这些扇形与磁道的相交面称为扇区 (Sector/Disk block)。为了管理的方便,有时候我们还会将多个扇区组合在一起形成一个簇 (Cluster),扇区一遍是最小的数据存储单元。一般而言,扇区的大小为512字节。
早期的磁盘每个磁道的扇区数量都是一样的,这就意味着半径越小的磁道位密度越大。而现在扇区不再按以前的方式划分了,现在磁盘的位密度都是一样大的,半径越大的磁道存储的数据越多。这就意味着外面磁道的扇区数要大于里面磁道的扇区数。
磁盘逻辑结构有两种:CHS模式和LBA模式。CHS(Cylinder-Head-Sector)是早期的硬盘数据寻址方式,直接反应硬盘的物理结构。数据的位置由柱面、磁头和扇区这三个参数确定。例如,数据存储在某个具体的柱面上,该柱面由特定的磁头访问,而数据具体位于该磁道上的某个扇区内。
另一种逻辑结构是LBA(Logical Block Addressing)。相比CHS,LBA可以很好地应对HDD和SSD这两种存储介质。LBA方式下,硬盘被视为一个连续的逻辑块序列,每个块包含固定数量的字节。每个逻辑块都有一个唯一的编。当系统需要访问硬盘上的数据时,它只需指定数据块的LBA编号即可。
CHS的磁头号和柱面号都是从0开始,而扇区号从1开始。LBA模式下,第1个扇区又是从0开始。它们有以下的对应法则:
主引导记录是硬盘最开始的那个扇区(是1号还是0号取决于寻址方式),MBR 对于整个 bootstrap 过程十分重要。其中有如下需要注意的点:
0x55AA
)作为有效性标志。这个标志用来表示该MBR是有效的,可以被BIOS识别。BIOS 是计算机系统中非常基础且关键的部件,它是一个固件(firmware),嵌入在计算机主板的一个芯片中。一旦计算机通电(或按下 reset 复位),CPU的PC寄存器就会写入 BIOS 中起始地址,即CPU执行的第一条指令来自 BIOS。BIOS 指令的地址是预定义的,有硬件电路完成,再计算机初始化的过程中就会把这个预定义的地址交给 PC 寄存器。
我们将传统的计算机启动方式和现代计算机启动方式区别开来。上述提到的 BIOS 属于传统 bootstrap 的范畴。而现代操作系统大多使用 UEFI。
Legacy Boot 使用 BIOS 作为固件接口,其启动流程包括加电、执行 BIOS 指令、电源自检 (POST)、加载主引导记录 (MBR),然后加载 Bootloader,将操作系统内核加载到内存中,并最终初始化系统完成启动。这种方式依赖于MBR 分区表,而 MBR 对磁盘容量的支持较为有限(最大为 2TB),且分区数量较少(最多 4 个主分区)。BIOS 详细的启动流程如下:
0x7C00H
处。MBR是硬盘的第一个扇区,包含启动加载程序(Bootloader)和分区表。UEFI(统一可扩展固件接口)是现代操作系统的主流选择。UEFI 的启动流程和BIOS很相似,相当于 BIOS 的 plus pro 版本。UEFI 改进了 BIOS 的功能,还扩展了其启动流程,提供了更丰富的启动界面功能,包括鼠标和键盘操作,甚至还支持启动时的网络连接。
UEFI 采用 GUID 分区表 (GPT),大幅提高了对磁盘容量(支持超过 2TB)和分区数量(最多 128 个或更多)的支持。此外,UEFI 还引入了安全启动机制,可校验引导加载器和操作系统的数字签名,增强系统的安全性,防止恶意软件的篡改。
相比 BIOS,UEFI 最重要的升级是支持 GUID 分区表,而 BIOS 支持的是 MBR。
GRUB 是一个开源的多引导加载器(Multiboot),是计算机启动时运行的第一个软件程序。GRUB 常用于多操作系统环境中,它负责选择加载某个操作系统内核并将控制权转交给内核,内核随后初始化操作系统的其余部分。
GRUB(Grand Unified Bootloader)的历史始于1995年,最初旨在支持GNU Hurd操作系统。它由Erich Boleyn开始开发,为了解决PC启动方法的不兼容性问题,他设计了多引导规范。1999年,Gordon Matzigkeit 和 Yoshinori K. Okuji 将 GRUB 官方纳入 GNU 项目。
GRUB 2 是一个重要的后续版本,提供了更多的功能和改进,尽管带上了版本号,但大家通常还是简称为 GRUB。为了区别,将老的 GRUB 称为 GRUB legacy,版本号停留在2005年的 v0.97。相比 GRUB 2,GRUB legacy 缺乏对 UEFI 的支持。
在GRUB中,你可以选择不同的操作系统。GRUB界面是字符模式,一般长这个样子:
我们通过阅读 GRUB legacy 源码片段,再深入了解一下 bootloader 的工作原理。
0x7C00
处,这是一个事先约定好的地址。至此,GRUB的 stage1加载完毕,CPU开始执行0x7C00
处的指令。/*stage1.s*/
jmp after _BPB /*第一条指令*/
...
after _BPB:
boot_drive_check:
...
testb $0x80, %dl /*通过测试dl寄存器是否为0x80来判断是否从硬盘启动*/
...
/*check if LBA is supported*/
mov $0x41, %ah
movw $0x55aa, %bx
int $0x13
/*
BIOS中断号:0x13
功能号:0x41
参数:0x55aa
功能:查询扩展的磁盘访问功能
返回结果:如果成功且bx寄存器返回相同魔数0x55aa,则说明支持LBA
*/
...
lba_mode:
/*为启动盘加载stage2代码做准备工作,比如从磁盘起始扇区、扇区数等*/
movl 0x10(%si), %ecx
movw $ABS(disk_address_packet), %si
movb $1, -1(%si)
movl $ABS(stage2_sector), %ebx
movw $0x0010, (%si)
movw $1, %2(%si)
movl %ebx, 8(%si)
movw $STAGE1_BUFFERSEG, 6(%si)
xorl %eax, %eax
movw %ax, 4(%si)
movl %eax, 12(%si)
movb $0x42, %ah
int $0x13
...
/*
BIOS中断号:0x13
功能号:0x42
参数:%dl = 驱动器编号
%si = offset of disk address packet
(DiskAddressPacket这个结构包含了要读取的扇区数、内存中缓冲区地址以及起始扇区号)
返回结果:读取成功%al寄存器设置为0x0,否则为错误代码
*/
stage2_address:
.word 0x8000
/*boot stage2*/
jmp *(stage2_address)
0x8000
,打开stage2部分的代码发现绝大部分是用C语言而非汇编,我们可以理解成stage1阶段已经设置了指令执行的基本环境,并且因为第二阶段的功能更为复杂,使用高级语言编写会降低开发难度。MBR(主引导记录)位于内存地址 0x7C00 后的 512 字节,而不是从 0x0000 到 0x01FF 的原因是历史和兼容性的考虑。在早期的 IBM PC 中,使用的 8088 处理器在启动时会将MBR加载到 0x7C00 这个地址。这是因为 8088 处理器需要在内存的前面部分(0x0000~0x03FF)保留用于中断向量表。此外,操作系统需要尽可能多的连续内存空间,所以MBR被放置在内存的尾部,即 0x7C00 地址,这样可以为操作系统留出更多的内存空间。为了兼容 8088,后续的 CPU 继续使用这个地址。
简单来说,计算机启动时,BIOS 会检查硬件,然后根据指定的顺序检查引导设备的第一个扇区(即MBR),并将其加载到内存地址 0x7C00 。MBR随后将控制权交给操作系统。这个过程确保了操作系统能够获得足够的内存空间,并且与早期的硬件保持兼容。为什么主引导记录的内存地址是0x7C00?
中断向量表(IVT)和BIOS的INT中断调用之间有直接的联系。在实模式下,BIOS利用IVT来处理中断请求。IVT是一个位于内存低地址的表,通常从0x00000
开始,它包含了256个中断向量,每个向量指向一个中断服务例程(ISR)的地址。当中断发生时,CPU会使用中断号来索引IVT并跳转到相应的ISR执行中断处理。
BIOS提供了一系列预定义的中断服务例程,这些例程可以通过软件中断调用(如INT 0x13
用于磁盘操作,INT 0x10
用于视频服务等)来访问。这些中断服务例程是实模式下与硬件交互的基本方法,允许操作系统和其他程序在不直接操作硬件的情况下执行诸如读取磁盘、显示字符等操作。
在保护模式下,中断描述符表(IDT)取代了IVT,但BIOS的INT调用仍然可以在实模式下使用。在系统启动时,BIOS会设置IVT,并在需要时响应中断请求,直到操作系统接管并可能设置自己的中断处理机制。
磁盘分区表:硬盘上存储分区信息的一个结构,它告诉计算机磁盘上有那些分区以及每个分区的起始和结束位置。这个表对操作系统来说非常重要,因为它用来读取、识别和管理磁盘上的数据。有几种不同的分区表类型,常见的包括MBR(主引导记录)和 GPT(GUID分区表)。
MBR 位于硬盘的最开始部分,它包含了引导代码和分区表。MBR 的大小通常为 512 字节,其中的bootloader占据446字节,分区表占据 64 字节,最后两字节是魔数 0x55AA 。
每个分区表中包含了一个分区的属性数据,主要有:
与 MBR 相比,GPT 是一个更现代的分区方案,它支持大于 2TB 的磁盘,并且可以创建多达128个分区。GPT 位于磁盘的开始和结束部分,提供冗余,以防主 GPT 损坏。GPT 使用全局唯一标识符(GUID)来标识分区,这意味着每个分区的标识符在全世界范围内都是唯一的。大多数现代操作系统(例如 Windows 10、Linux、macOS )都支持 GPT。但是,使用 GPT 可能需要 UEFI 固件,而不是传统的 BIOS 。
GPT 每个分区条目 128 字节包含以下字段:分区类型 GUID (16字节)、起唯一分区 GUID (16字节)、起始 LBA (8字节)、结束 LBA (8字节)、属性标志(8字节)、分区名称(72字节)。这种分区表属性决定了 每个分区最大容量:
现阶段,引导程序加载操作系统内核,内核启动的时候,会有两种比较常见的系统启动方式:
init 是一个守护进程(Deamon),开机运行,关机结束。init 进程是进程号为1的进程(pid = 1),所有系统上运行的其他进程都是 init 进程所 fork()
出来的。若当 init 进程无法运行,则系统就不会允许有其他任何程序运行,这种状态也被称为 "system panic" 。
一般我们常把 init 和 SysVinit 所联系起来。但实际上根据不同的 linux distribution ,init进程可以是SysVinit、Upstart或Systemd。
systemd 是一种系统和服务管理器,旨在成为 Linux 系统的 init 系统的替代品。它提供了并行化启动服务、按需启动守护进程、按需挂载文件系统、快照和恢复系统状态等功能。
相比init必须一个进程接着一个串行的系统启动,systemd支持多线程并行的启动方式,提高系统的启动速度。除此之外,systemd使用依赖关系图来确定服务的启动顺序,增强了系统的稳定性,但也提高了复杂性。
早期Linux系统开机的几个阶段:
BIOS开机自检到加载内核阶段。
启动 init 进程。
init 进程读取/etc/inittab
文件。
# Default runlevel. The runlevels used by RHS are:
# 0 - halt (Do NOT set initdefault to this)
# 1 - Single user mode
# 2 - Multiuser, without NFS (The same as 3, if you do not have networking)
# 3 - Full multiuser mode
# 4 - unused
# 5 - X11
# 6 - reboot (Do NOT set initdefault to this)
#
id:5:initdefault:
id:5:initdefault:
,表示 init 要启动的进程会完成操作系统5级的功能。执行系统初始化脚本(/etc/rc.d/rc.sysinit
)。
/etc/inittab
每一行代表一个执行项。id:runlevels:action:process
执行启动层级对应的脚本(/etc/rc*.d
)。
/etc/inittab
文件中记录的运行级别5。所以init进程执行/etc/rc5.d
进程,进行5运行级别必要的操作。启动终端
rc.local
文件。1:2345:respawn:/sbin/mingetty tty1
2:2345:respawn:/sbin/mingetty tty2
3:2345:respawn:/sbin/mingetty tty3
4:2345:respawn:/sbin/mingetty tty4
5:2345:respawn:/sbin/mingetty tty5
6:2345:respawn:/sbin/mingetty tty6
rc.local
文件中。之后有了service系统,就不需要手动更改配置文件了。在有些时候,我们用ps -ef
查看系统上运行所有进程的信息时,我们会发现进程号为1的进程并不是systemd。这时我们打印一下/sbin/init
的进程信息,会发现/sbin/init
只是一个软链接文件,实际上指向的还是/lib/systemd/systemd
文件。
du@DVM:/$ ps -ef | more
UID PID PPID C STIME TTY TIME CMD
root 1 0 0 16:19 ? 00:00:03 /sbin/init auto noprompt splash
du@DVM:/$ ls -l /sbin/init
lrwxrwxrwx 1 root root 20 11月 22 2023 /sbin/init -> /lib/systemd/systemd